package org.budo.support.ibatis;

import java.sql.Connection;
import java.util.Properties;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 * @see com.github.pagehelper.PageInterceptor
 */
@Intercepts({ //
        @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class }), //
        @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }), //
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class }), //
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class }) //
})
@Slf4j
public class BudoMybatisInterceptor implements Interceptor {
    private static final ThreadLocal<Invocation> INVOCATION_THREAD_LOCAL = new InheritableThreadLocal<Invocation>();

    public static MappedStatement getThreadLocalMappedStatement() {
        Invocation invocation = INVOCATION_THREAD_LOCAL.get();
        if (null == invocation) {
            log.debug("#34 ThreadLocal<Invocation> is null");
            return null;
        }

        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];

        return mappedStatement;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        if (null == args || args.length < 1) {
            return invocation.proceed();
        }

        Object arg_0 = args[0];
        if (null == arg_0 || !(arg_0 instanceof MappedStatement)) {
            return invocation.proceed();
        }

        String statementId = null;
        try {
            MappedStatement mappedStatement = (MappedStatement) arg_0;
            statementId = mappedStatement.getId();

            log.debug("#32 before intercept, invocation={}, statementId={}", invocation, statementId);

            INVOCATION_THREAD_LOCAL.set(invocation);
            return invocation.proceed();
        } finally {
            // INVOCATION_THREAD_LOCAL.remove(); // 同一个事务其他请求?

            log.debug("#54 after intercept finally, invocation={}, statementId={}", invocation, statementId);
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}